package com.proton.carepatchtemp.database;

import android.text.TextUtils;

import com.apkfuns.logutils.LogUtils;
import com.google.gson.Gson;
import com.proton.carepatchtemp.application.VCare;
import com.proton.carepatchtemp.constant.Codes;
import com.proton.carepatchtemp.constant.Extras;
import com.proton.carepatchtemp.constant.Settings;
import com.proton.carepatchtemp.constant.XTypes;
import com.proton.carepatchtemp.database.dao.AddReportInfo;
import com.proton.carepatchtemp.database.table.Device;
import com.proton.carepatchtemp.database.table.Profile;
import com.proton.carepatchtemp.database.table.Report;
import com.proton.carepatchtemp.database.table.ShareHistory;
import com.proton.carepatchtemp.utils.FileUtils;
import com.proton.carepatchtemp.utils.FormatUtils;
import com.proton.carepatchtemp.utils.JNIUtils;
import com.proton.carepatchtemp.utils.NumUtils;
import com.proton.carepatchtemp.utils.SpUtils;
import com.proton.carepatchtemp.utils.ThreadUtils;
import com.proton.carepatchtemp.utils.eventbus.MainEvent;
import com.proton.carepatchtemp.utils.net.ReportCenter;
import com.proton.carepatchtemp.utils.net.XMPPCenter;
import com.proton.carepatchtemp.utils.net.oss.OSSUtils;

import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.proton.carepatchtemp.constant.Config.WARNING_DIV_DEFAULT;
import static com.proton.carepatchtemp.constant.Settings.TEMP_LOAD_TIME_DIV;

/**
 * Created by MoonlightSW on 2016/11/23.
 */

public class RealData implements Serializable {
    //波动阈值
    public static final float STH = 0.0001F;
    public static final float K_HIGH = 1.8F;
    //斜率分界点
    public static float K_LOW = 0.05F;
    public int msgCount = 0;
    //  添加报告信息
    public AddReportInfo mAddReportInfo = new AddReportInfo();
    //  实时测试信息
    public float warningHigh = 37.5f;
    public float warningLow = 35.0f;
    //  实时温度
    public Float realTemp;
    //  最高温度
    public Float highestTemp = 0F;
    //  最低温度
    public Float lowestTemp = 0F;
    //  是否上传过
    public boolean isUpload = false;
    //  是否获取到ReportId
    public boolean isGetRptID = false;
    //  开始测量的时间
    public Long startTime;
    //  断开的时间
    public Long disconnectedTime;
    //  是否获取过第一次的历史数据
    public boolean isFirstConnnect = true;
    //  第一次测量时的历史数据
    public List<Float> firstHistory = new ArrayList<>();
    //  是否正在测量
    public boolean isTesting = false;
    // 全部的温度数据
    public List<Float> allTemps = new ArrayList<>();
    //是否需要绘制实时图表
    public boolean drawRealLine = false;
    //  测量信息
    public List<Double> mAlerts = new ArrayList<>();
    //  添加的随手记信息
    public List<Integer> mIndex = new ArrayList<>();
    public List<Integer> mTypes = new ArrayList<>();
    public List<Long> mTime = new ArrayList<>();
    /**********************************************
     *             温度值稳定相关算法             *
     *      第一次达到夹紧稳定后采用算法处理      *
     *   稳定条件：①连续十组数据的方差小于阈值   *
     *              ②实时测量值大于36            *
     *                  START                     *
     **********************************************/

    public Float tempS = 0F;
    // 是否稳定标志位
    public boolean isSta = false;
    //第一次夹紧稳定标志位
    public boolean isFirstSta = false;
    // 是否夹紧后稳定
    public boolean isForceSta = false;
    //算法处理后的数据
    public List<Float> afterDealTemps = new ArrayList<>();
    //判断是否稳定的数据
    public List<Float> staTemps = new ArrayList<>();
    //求斜率数组
    public List<Float> kTemps = new ArrayList<>();
    //实时的稳定值
    public Float realStaTemps;
    //真实实时温度
    public float mRealTemp;
    //  档案信息
    public Profile profile;
    //  设备信息
    public Device device;
    //共享列表
    public List<ShareHistory.SharelistBean> shareList = Collections.synchronizedList(new ArrayList<>());
    //  报告信息
    public Report report;
    DecimalFormat format = new DecimalFormat("###,###,###.00");
    //  缓存数据保存线程的线程计数，防止在提交的时候数据保存不完整（如果需要本地保存）
    private long saveCacheCount = 0;
    //波动点
    private int wedIndex = 0;
    //是否进入算法标志位
    private boolean isSuanfa = true;
    //是否撕下来标志位
    private boolean isTakeDown = false;
    private int takeDownIndex = 0;
    //撕下时的显示温度
    private Float takeDownTemps;
    //撕下后k大于0.5的点
    private int k5TakeDownIndex = 0;
    private TempImg lastTempImg = new TempImg();

    public void updateStaData(Float realTemp) {
        if (allTemps.size() > 15) {
            staTemps.clear();
            kTemps.clear();

            staTemps.addAll(allTemps.subList(allTemps.size() - 10, allTemps.size()));
            kTemps.addAll(allTemps.subList(allTemps.size() - 15, allTemps.size()));
            LogUtils.d("staTemps = " + staTemps.toString());
            return;
        }

        if (staTemps.size() < 10) {
            staTemps.add(realTemp);
        } else if (staTemps.size() >= 10) {
            while (staTemps.size() > 10) {
                staTemps.remove(0);
            }
            staTemps.add(realTemp);
        }

        if (kTemps.size() < 15) {
            kTemps.add(realTemp);
        } else {
            while (kTemps.size() > 15) {
                kTemps.remove(0);
            }
            kTemps.add(realTemp);
        }
    }

    public Float getK() {
        if (kTemps.size() != 15) {
            return null;
        } else {
            return kTemps.get(kTemps.size() - 1) - kTemps.get(0);
        }
    }

    public boolean isForceSta(Float real) {
        if (staTemps.size() != 10) {
            return false;
        }
        return real > 36.0F && NumUtils.getVariance(staTemps) < STH;
    }

    public boolean isTempSta() {
        if (staTemps.size() != 10) {
            return false;
        }

        return NumUtils.getVariance(staTemps) < STH;
    }

    public boolean isTempSta(List<Float> temps) {
        if (temps.size() != 10) {
            return false;
        }

        return NumUtils.getVariance(temps) < STH;
    }

    /**********************************************
     *             温度值稳定相关算法             *
     *                                            *
     *                    END                     *
     **********************************************/


    public Long getTestTime() {
        return allTemps.size() * Settings.TEMP_LOAD_TIME_DIV;
    }

    /**
     * 设置实时数据
     *
     * @param temp 体温贴获取到的实时数据
     */
    public void setRealTemp(Float temp) {
        LogUtils.d("Real time RealTEMP  = " + temp);
        mRealTemp = temp;
        //初始化temps
        if (tempS == 0) {
            tempS = temp;
        }
        //原始数据保存
        allTemps.add(temp);

        float[] doubles = new float[allTemps.size()];
        for (int i = 0; i < allTemps.size(); i++) {
            doubles[i] = allTemps.get(i);
        }
        LogUtils.e("算法处理前:" + temp);
        lastTempImg = JNIUtils.getTemp(doubles, lastTempImg);
        temp = lastTempImg.stabT;
        LogUtils.e("算法处理后:" + temp);

//        long lostTime = System.currentTimeMillis() - startTime - allTemps.size() * TEMP_LOAD_TIME_DIV;
//        //原始数据填充
//        realTempPatch(lostTime, temp);
//        //更新算法所需要的数据
//        updateStaData(temp);r

        tempS = temp;
        // 处理后数据保存
        afterDealTemps.add(tempS);

        //算法数据填充
//        long lostTime1 = System.currentTimeMillis() - startTime - afterDealTemps.size() * TEMP_LOAD_TIME_DIV;
//        afterTempPatch(lostTime1, tempS);

        this.realTemp = tempS;
        //绘制实时曲线
        if (drawRealLine) {
            EventBus.getDefault().post(new MainEvent(getDeviceMac(), Codes.REPORT_REAL_TIME_DRAW, String.valueOf(tempS), String.valueOf(temp)));
        }
        LogUtils.d("Real time predicted TEMP  = " + realTemp);

        //实时共享发送
        sendShareMsg(XTypes.STATUS_INFO, tempS);

        //高温点添加
        if (temp > warningHigh) {
            mAlerts.add(FormatUtils.float2double(temp));
        }

        if (null != startTime && null != getDeviceID() && null == getReportID()) {
            //获取报告id
            mAddReportInfo.setDeviceid(getDeviceID());
            mAddReportInfo.setProfileid(getProfileID());
            mAddReportInfo.setStoreStartTime(startTime);
            mAddReportInfo.setMacAddress(device.getBtaddress());
            ReportCenter.add(mAddReportInfo);
        }

        if (highestTemp < Collections.max(allTemps)) {
            highestTemp = Collections.max(allTemps);
        }

        if (lowestTemp > Collections.min(allTemps)) {
            lowestTemp = Collections.min(allTemps);
        }
    }

    /**
     * 真实数据补全
     */
    private void realTempPatch(long lostTime, float temp) {
        //数据校正
        //                                    % x2和x1中间间隔n个点的处理方式
        //                                    % 一共有n+1个间隔
        //                                    del = x2-x1;
        //                                    a = del/n/(n+1);
        //                                    k = del/2/(n+1);
        //                                    x(i)=x(i-1)+k+(n-i+1)*a;
        int n = (int) (lostTime / Settings.TEMP_LOAD_TIME_DIV);
        if (lostTime <= 60 * 60 * 60 * 1000) {
            if (n > 0) {
                float[] floats = new float[n + 1];

                float x0 = allTemps.get(allTemps.size() - 1);
                float xn1 = temp;
                float del = xn1 - x0;

                floats[0] = x0;

                float a = del / (float) n / (n + 1);
                float k = del / 2F / (float) (n + 1);
                DecimalFormat decimalFormat = new DecimalFormat(".0");
                for (int i = 0; i < n; i++) {
                    allTemps.add(Float.valueOf(decimalFormat.format(floats[i] + k + (float) (n - i) * a)));
                    floats[i + 1] = floats[i] + k + (float) (n - i) * a;
                }
                LogUtils.i("Real Time Patch Temp Data!");
                LogUtils.d(floats);
            }
        } else {
            for (int i = 0; i < n; i++) {
                allTemps.add(0F);
            }
        }
    }

    /**
     * 算法数据补全
     */
    private void afterTempPatch(long lostTime, float temp) {
        //数据校正
        //                                    % x2和x1中间间隔n个点的处理方式
        //                                    % 一共有n+1个间隔
        //                                    del = x2-x1;
        //                                    a = del/n/(n+1);
        //                                    k = del/2/(n+1);
        //                                    x(i)=x(i-1)+k+(n-i+1)*a;
        int n = (int) (lostTime / Settings.TEMP_LOAD_TIME_DIV);
        if (lostTime <= 60 * 60 * 60 * 1000) {
            if (n > 0) {
                float[] floats = new float[n + 1];

                float x0 = afterDealTemps.get(afterDealTemps.size() - 1);
                float xn1 = temp;
                float del = xn1 - x0;

                floats[0] = x0;

                float a = del / (float) n / (n + 1);
                float k = del / 2F / (float) (n + 1);
                DecimalFormat decimalFormat = new DecimalFormat(".00");
                int size = allTemps.size();

                try {
                    for (int i = 0; i < n; i++) {
                        if (allTemps.get(size - n + i - 1) <= (floats[i] + k + (float) (n - i) * a)) {
                            afterDealTemps.add(Float.valueOf(decimalFormat.format(floats[i] + k + (float) (n - i) * a)));
                        } else {
                            afterDealTemps.add(allTemps.get(size - n - i - 1));
                        }

                        floats[i + 1] = floats[i] + k + (float) (n - i) * a;
                    }
                    if (afterDealTemps.size() < allTemps.size()) {
                        int lost = afterDealTemps.size() - allTemps.size();
                        Float temps = afterDealTemps.get(afterDealTemps.size() - 1);
                        for (int i = 0; i < lost; i++) {
                            afterDealTemps.add(temps);
                        }
                    }
                } catch (Throwable throwable) {
                    LogUtils.e(throwable);
                }


                LogUtils.i("After Temps Patch Temp Data!");
                LogUtils.d(floats);
            }
        } else {
            for (int i = 0; i < n; i++) {
                afterDealTemps.add(0F);
            }
        }
    }

    private void sendShareMsg(int type, float temp) {
        if (!isGetRptID) {
            return;
        }
        msgCount += 1;
        ThreadUtils.runOnOtherThread(() -> {
            XmppMsg tempXmpp = new XmppMsg();
            if (null == report || null == report.getId()) {
                msgCount -= 1;
                return;
            }
            if (profile != null) {
                tempXmpp.displayAvatar = profile.getAvatar();
                tempXmpp.setDisplayName(profile.getRealname());
            }
            tempXmpp.setDisplayMax(highestTemp);
            tempXmpp.setDeviceMac(getDeviceMac());
            tempXmpp.setDisplayCurrent(temp);
            tempXmpp.setNow(System.currentTimeMillis());
            tempXmpp.setReportId(report.getId());
            tempXmpp.setDisplayStatus(type);
            tempXmpp.setUid(VCare.get().getUid());
            for (ShareHistory.SharelistBean sb :
                    shareList) {
                if (sb.isCanSend()) {
                    LogUtils.d(sb);
                    LogUtils.d(tempXmpp);
                    XMPPCenter.sendMsgCustom(tempXmpp, sb.getUid());
                }
            }

            msgCount -= 1;
        });
    }

    public void handleCacheTemps(List<Float> cache) {

        allTemps.addAll(cache);
        if (isFirstConnnect) {
            firstHistory.addAll(cache);
        }

        //处理后数据添加缓存
        for (Float f :
                cache) {
            if (f > tempS) {
                afterDealTemps.add(f);
            } else {
                afterDealTemps.add(tempS);
            }
        }
        //需要保存数据到本地的情况
//        saveCacheCount += 1;
//
//        ThreadUtils.postCacheData(() -> {
//            saveTempsData(cache);
//            saveCacheCount -= 1;
//        });
//
    }

    public void patchCacheTemps(List<Float> cache) {

        if (highestTemp < Collections.max(cache)) {
            highestTemp = Collections.max(cache);
        }

        if (lowestTemp > Collections.min(cache)) {
            lowestTemp = Collections.min(cache);
        }

        if (null != disconnectedTime) {
            if (startTime + allTemps.size() * TEMP_LOAD_TIME_DIV < System.currentTimeMillis()) {

                long lostTime = disconnectedTime - startTime - allTemps.size() * TEMP_LOAD_TIME_DIV - cache.size() * TEMP_LOAD_TIME_DIV;

                //                                    % x2和x1中间间隔n个点的处理方式
                //                                    % 一共有n+1个间隔
                //                                    del = x2-x1;
                //                                    a = del/n/(n+1);
                //                                    k = del/2/(n+1);
                //                                    x(i)=x(i-1)+k+(n-i+1)*a;
                int n = (int) (lostTime / Settings.TEMP_LOAD_TIME_DIV);
                if (n > 0 && lostTime <= 60 * 60 * 1000) {
                    float[] floats = new float[n + 1];

                    float x0 = allTemps.get(allTemps.size() - 1);
                    float xn1 = cache.get(0);
                    float del = xn1 - x0;

                    floats[0] = x0;

                    float a = del / (float) n / (n + 1);
                    float k = del / 2F / (float) (n + 1);
                    DecimalFormat decimalFormat = new DecimalFormat(".00");
                    for (int i = 0; i <= n; i++) {
                        cache.add(Float.valueOf(decimalFormat.format(floats[i] + k + (float) (n - i) * a)));
                        floats[i + 1] = floats[i] + k + (float) (n - i) * a;
                    }

                    LogUtils.i("Need Patch Temp Data !");
                    LogUtils.d(floats);
                } else {
                    for (int i = 0; i < n; i++) {
                        cache.add(0F);
                    }
                }


//                //需要防止数据过多
//                for (int i = 0; i < lostTime % TEMP_LOAD_TIME_DIV && startTime + allTemps.size() * Settings.TEMP_LOAD_TIME_DIV <= System.currentTimeMillis(); i++) {
//                    LogUtils.d(FormatUtils.transferLongToDate("MM/ dd/yyyy HH:mm:ss:ms", System.currentTimeMillis()));
//                    allTemps.add(0F);
//                }
//
//                if (lostTime % TEMP_LOAD_TIME_DIV > 4000) {
//                    allTemps.add(0F);
//                }
            }
        }

        //第一次的缓存数据需要保存下来
        if (isFirstConnnect) {
            firstHistory.addAll(cache);
        }

        allTemps.addAll(cache);
        for (Float f :
                cache) {
            if (f > tempS) {
                afterDealTemps.add(f);
            } else {
                afterDealTemps.add(tempS);
            }
        }
        //需要保存数据到本地的情况
//        saveCacheCount += 1;
//
//        ThreadUtils.postCacheData(() -> {
//            saveTempsData(cache);
//            saveCacheCount -= 1;
//        });
//
    }

    public void patchTempData() {
        if (null != disconnectedTime) {
            if (startTime + allTemps.size() * TEMP_LOAD_TIME_DIV < disconnectedTime) {
                long lostTime = disconnectedTime - startTime - allTemps.size() * TEMP_LOAD_TIME_DIV;
                ThreadUtils.runOnOtherThread(() -> {
                    for (int i = 0; i < lostTime % TEMP_LOAD_TIME_DIV; i++) {
                        allTemps.add(0F);
                        afterDealTemps.add(tempS);
                    }
                    if (lostTime % TEMP_LOAD_TIME_DIV > 4000) {
                        allTemps.add(0F);
                        afterDealTemps.add(tempS);
                    }
                });
            }
        }
    }

    public void addNotePoint(int type, long time) {
        mIndex.add(allTemps.size() - 1);
        mTypes.add(type);
        mTime.add(time);
    }

    /**
     * @param temps 本地保存温度数据数组
     */
    public void saveTempsData(List<Float> temps) {
        String content = temps.toString().replace("[", ",").replace("]", "");
        String filePath = FileUtils.getDataCache() + File.separator + startTime + "_TempCache";
        LogUtils.d(filePath);
        File file = new File(FileUtils.getDataCache(), startTime + "_TempCache");
        FileWriter mWriter = null;
        try {
            mWriter = new FileWriter(file, true);
            mWriter.write(content);
            mWriter.flush();
        } catch (IOException e) {
            LogUtils.e(e);
        } finally {
            try {
                if (null != mWriter) {
                    mWriter.close();
                }
            } catch (IOException e) {
                LogUtils.e(e);
            }
        }

    }

    public void upLoadTempData() {
        ThreadUtils.runOnOtherThread(() -> {
            try {
                JSONObject jsonObject = floatList2String();
                //存到本地文件，用于比较
                String filePath = OSSUtils.uploadJson(jsonObject.toString(), OSSUtils.getUploadKey(getReportID(), VCare.get().getUser()));
                LogUtils.e(filePath);
                if (TextUtils.isEmpty(filePath)) {
                    LogUtils.e("OSS filepath upload failed!");
                }

                mAddReportInfo.setReportId(getReportID());
                mAddReportInfo.setDeviceid(getReportID());
                mAddReportInfo.setFilepath(filePath);
                mAddReportInfo.setProfileid(getProfileID());
                mAddReportInfo.setStoreStartTime(startTime);
                mAddReportInfo.setStoreEndTime(System.currentTimeMillis());

                Report.DataBean dataBean = new Report.DataBean();
                dataBean.setTemp_max(String.valueOf(highestTemp));
                dataBean.setTime(System.currentTimeMillis() - startTime);
                dataBean.setHeart_rate_avg(warningHigh);
                dataBean.setConception_rate(warningLow);
                Gson gson = new Gson();

                String data = gson.toJson(dataBean);

                mAddReportInfo.setData(data);

                if (TextUtils.isEmpty(String.valueOf(mAddReportInfo.getReportId()))) {
                    mAddReportInfo.setData(getReportID() + "");
                }
                while (saveCacheCount > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                ReportCenter.edit(mAddReportInfo);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        });
    }

    public JSONObject floatList2String() throws JSONException {
        JSONObject mJsonObject = new JSONObject();
        //读取数组
//        JSONArray floats = new JSONArray(FileUtils.readTempCacheFile(tag.hashCode() + ""));
//        JSONArray floats = new JSONArray(FileUtils.readTempCacheFile(String.valueOf(TestManager.get(tag).reportId)));

        long lost = System.currentTimeMillis() - startTime - getTestTime();
        if (lost / TEMP_LOAD_TIME_DIV > 0) {
            for (int i = 0; i < lost / TEMP_LOAD_TIME_DIV; i++) {
                allTemps.add(0F);
            }
            LogUtils.d("Add Lost Temp Data!");
        }
        mJsonObject.put(Extras.RPT_FLOATS, new JSONArray(afterDealTemps.toString()));
        mJsonObject.put(Extras.RPT_RAW_DATA, new JSONArray(allTemps.toString()));
        mJsonObject.put(Extras.RPT_DEV_VERSION, device.getVersion());
        mJsonObject.put(Extras.RPT_MAX_TEMP, FormatUtils.float2double(highestTemp)); //
        mJsonObject.put(Extras.RPT_ALERT_INVERTAL, SpUtils.getLong(Extras.WARNING_TIME_DIV, WARNING_DIV_DEFAULT));
        mJsonObject.put(Extras.RPT_MIN_TEMP, FormatUtils.float2double(lowestTemp));
        mJsonObject.put(Extras.RPT_TIME_INTERVAL, 2000);
        mJsonObject.put(Extras.RPT_ALERT_MAX, FormatUtils.float2double(warningHigh));

        mJsonObject.put(Extras.RPT_ALERTS, mAlerts);

        JSONObject mMemo = new JSONObject();

        mMemo.put(Extras.RPT_MEMO_INDEX, mIndex);
        mMemo.put(Extras.RPT_MEMO_TYPE, mTypes);
        mMemo.put(Extras.RPT_MEMO_TIME, mTime);

        mJsonObject.put(Extras.RPT_MEMO, mMemo);
        mJsonObject.put(Extras.RPT_DEVICE_ID, device.getId());
        mJsonObject.put(Extras.RPT_CREATOR, VCare.get().getUid());
        mJsonObject.put(Extras.RPT_ENDTIME_JSON, System.currentTimeMillis());
        mJsonObject.put(Extras.RPT_REPORT_ID, getReportID());
        mJsonObject.put(Extras.RPT_STARTTIME_JSON, startTime);
        mJsonObject.put(Extras.RPT_PRF_NAME, profile.getRealname());
        mJsonObject.put(Extras.RPT_ALERT_MIN, FormatUtils.float2double(warningLow));

        return mJsonObject;
    }

    /**
     * @param reportId
     * @param content
     * @throws IOException
     */
    public void writeFile(String reportId, String content) throws IOException {
        String filePath = FileUtils.getDirectoryP("DataCache") + File.separator + reportId;
        FileOutputStream fos = new FileOutputStream(filePath);
        byte[] bytes = content.getBytes();
        fos.write(bytes);
        fos.flush();
        fos.close();
    }

    public Report getReport() {
        return report;
    }

    public void setReport(Report report) {
        this.report = report;
    }

    public Long getReportID() {
        return null == this.report ? null : this.report.getId();
    }

    public Device getDevice() {
        return device;
    }

    public void setDevice(Device device) {
        this.device = device;
    }

    public Long getDeviceID() {
        return this.device.getId();
    }

    public String getDeviceMac() {
        return this.device.getBtaddress();
    }

    public String getDeviceSN() {
        return this.device.getSn();
    }

    public Profile getProfile() {
        return profile;
    }

    public void setProfile(Profile profile) {
        this.profile = profile;
    }

    public Long getProfileID() {
        return this.profile.getId();
    }


}
